/*
 *  Copyright 2011 yura.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.db_vdumper.servicesimpl;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opu.db_vdumper.domain.DColumn;
import org.opu.db_vdumper.domain.DColumnRelation;
import org.opu.db_vdumper.domain.DTable;
import org.opu.db_vdumper.services.DomainService;
import org.opu.db_vdumper.util.DbManager;
import org.opu.db_vdumper.util.Logger;
import org.opu.db_vdumper.util.QueryHandler;
import static org.opu.db_vdumper.util.QueryHandler.*;

/**
 *
 * @author yura
 */
public class DomainServiceImpl implements  DomainService{

    private DbManager db;
    
    private Map<String, DTable> tables;
    private Map<String, List<String>> schemas;
    private Map<DTable, Map<String, DColumn>> columns;
    private boolean init = false;

    public DomainServiceImpl(DbManager dbManager) {
        if (dbManager == null){
            throw new IllegalArgumentException("DbManager can't be null!",
                    new NullPointerException());
        }
        this.db = dbManager;
    }

    private void init() throws SQLException {
        tables = tables();
        columns = columns(tables);
        schemas = schemas(tables);

        relation(tables, columns);

        init = true;
    }

    private Map<String, DTable> tables() throws SQLException{
        Map<String, DTable> _tables = new HashMap<String, DTable>();
        QueryHandler qh = db.getQueryHandler();

        if (!qh.sendQueryGetAllTables()) {
            throw new SQLException("Can't get table list");
        }
        
        for (int i = 0; i < db.getRows(); i++) {
            String tn = db.getString(i + 1, TABLE_NAME);
            String ts = db.getString(i + 1, TABLE_SCHEM);
            _tables.put(ts + "." + tn, new DTable(ts, tn, null));
        }

        Logger.getInstance().debug(this, "Table plain list: " + _tables.keySet());

        for (DTable tbl : _tables.values()) {
            if (! qh.sendQueryGetColumns(null, tbl.getSchema(), tbl.getName())){
                throw new SQLException("Can't get columns for " + tbl);
            }

            for (int i = 0; i < db.getRows(); i++) {
                String cn = db.getString(i + 1, COLUMN_NAME);
                String ct = db.getString(i + 1, TYPE_NAME);
                tbl.add(new DColumn(tbl, cn, ct));
            }
        }

        Logger.getInstance().debug(this, "Table list: " + _tables.values());

        return _tables;
    }

    private Map<DTable, Map<String, DColumn>> columns(Map<String, DTable> tbls){
        Map<DTable, Map<String, DColumn>> _columns = 
                new HashMap<DTable, Map<String, DColumn>>();

        for (DTable t : tbls.values()) {
            List<DColumn> cs = t.getColumns();
            Map<String, DColumn> map = new HashMap<String, DColumn>();
            for (DColumn c : cs) {
                map.put(c.getName(), c);
            }
            _columns.put(t, map);
        }
        return _columns;
    }

    /**
     *  <ol><b>Algorithm of work based on:</b>
     *  <li>System table can't have reference to user table</li>
     *  <li>If user table have reference on system table, not need to copy system table.</li>
     * </ol>
     *
     * @param tables all tables
     * @param columns all columns
     * @throws SQLException 
     */
    private void relation(Map<String, DTable> tables,
            Map<DTable, Map<String, DColumn>> columns) throws SQLException{

        QueryHandler qh = db.getQueryHandler();
        for (DTable tbl : tables.values()) {
            if (!qh.sendQueryGetForeignKeys(null, tbl.getSchema(), tbl.getName())) {
                throw new SQLException("Can't get foreign keys");
            }

            for (int i = 0; i < db.getRows(); i++) {
                String tn = db.getString(i + 1, FKTABLE_NAME);
                String ts = db.getString(i + 1, FKTABLE_SCHEM);
                String cn = db.getString(i + 1, FKCOLUMN_NAME);

                DTable table = tables.get(ts + "." + tn);
                if (table == null) {
                    // Probably it's system table that have reference to another table
                    Logger.getInstance().debug(this, "relation skip: <" + ts + "." + tn + ">");
                    continue;
                }

                Map<String, DColumn> colMap = columns.get(table);
                if (colMap == null) {
                    throw new SQLException("desynchronisation database table <"
                            + ts + ", " + tn + ">  (no columns in table)");
                }

                DColumn col = colMap.get(cn);
                if (col == null) {
                    throw new SQLException("desynchronisation database "
                            + "column <" + ts + ", " + tn + ", " + cn + ">");
                }

                String tn2 = db.getString(i + 1, PKTABLE_NAME);
                String ts2 = db.getString(i + 1, PKTABLE_SCHEM);
                String cn2 = db.getString(i + 1, PKCOLUMN_NAME);

                if (tables.get(ts2 + "." + tn2) != null) {
                    // If referenced table is not system table
                    table.addRelation(new DColumnRelation(col, ts2, tn2, cn2));
                    Logger.getInstance().debug(this, "add relation: from <" + ts
                            + "." + tn + "." + cn + "> to <" + ts2 + "." + tn2
                            + "." + cn2 + ">");
                } else {
                    Logger.getInstance().debug(this, "relation skip: <" + tn + "."
                            + ts + "> to <" + ts2 + "." + tn2 + ">");
                }
            }
        }
    }

    private Map<String, List<String>> schemas(Map<String, DTable> tables){
        Map<String, List<String>> map = new HashMap<String, List<String>>();

        for (String name : tables.keySet()) {//schema.tableName
            DTable tbl = tables.get(name);
            String schema = tbl.getSchema(); //schema

            List<String> get = map.get(schema);
            if (get == null){
                get = new ArrayList<String>();
                map.put(schema, get);
            }

            get.add(name);
        }

        return map;
    }

    public DbManager getDb() {
        return db;
    }

    @Override
    public void reinit() throws SQLException{
        init();
    }

    @Override
    public Map<DTable, Map<String, DColumn>> getColumns() throws SQLException {
        if (!init){
            init();
        }
        return columns;
    }

    @Override
    public Map<String, List<String>> getSchemas() throws SQLException {
        if (!init){
            init();
        }
        return schemas;
    }

    @Override
    public Map<String, DTable> getTables() throws SQLException {
        if (!init){
            init();
        }
        return tables;
    }

    @Override
    public List<DTable> getAllDTables() throws SQLException {
        if (!init){
            init();
        }
        return new ArrayList<DTable>(tables.values());
    }

}
